#include "shmipc/com/xmessage.h"



namespace shmipc::com {

namespace XMessageHelper {

bool CheckMessageHeader(const uint8_t* data, uint32_t size) {
    return (size >= XMSG_BASE_HDR_SIZE) && (ByteRead4(data) == size) && 
           (data[XMSG_FIELD_OFFSET_STARTCODE] == XMSG_START_CODE_BYTE1) && (data[XMSG_FIELD_OFFSET_STARTCODE + 1] == XMSG_START_CODE_BYTE2) &&
           (XMSG_GET_PROTOCOL_VER(data[XMSG_FIELD_OFFSET_FLAGS]) == XMSG_PROTOCOL_VER) &&
           (data[XMSG_FIELD_OFFSET_TYPE] != XMSG_UNKNOWN_TYPE);
}

bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags) {
    return InitializeMessageHeader(data, length, flags, XMSG_UNKNOWN_TYPE, 0, 0);
}

bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags, uint8_t type) {
    return InitializeMessageHeader(data, length, flags, type, 0, 0);
}

bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags, uint8_t type, MessageId msg_id) {
    return InitializeMessageHeader(data, length, flags, type, msg_id, 0);
}

bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags, uint8_t type, MessageId msg_id, SequenceNumber seq_num) {
    ByteWrite4(data, length);
    MsgHdrSetLength(data, length);
    MsgHdrSetStartCode(data, XMSG_START_CODE);
    MsgHdrSetFlags(data, flags);
    MsgHdrSetType(data, type);
    MsgHdrSetMsgId(data, msg_id);
    MsgHdrSetSeqNum(data, seq_num);

    return true;
}

//static 
XMessageType XMsgGetResponseType(XMessageType request_type) {
    switch (request_type) {
    case XMsg_TypeMethodRequest:            return XMsg_TypeMethodResponse; break;
    case XMsg_TypeEventStopSubscribe:       return XMsg_TypeEventStopSubscribeAck; break;
    case XMsg_TypeEventStopSubscribeGroup:  return XMsg_TypeEventStopSubscribeAck; break;
    case XMsg_TypeEventSubscribeGroup:      return XMsg_TypeEventSubscribeAck; break;
    case XMsg_TypeEventSubscribe:           return XMsg_TypeEventSubscribeAck; break;
    case XMsg_TypePropertyGet:              return XMsg_TypePropertyGetAck; break;
    case XMsg_TypePropertySet:              return XMsg_TypePropertySetAck; break;
    case XMsg_TypePropertySubscribe:        return XMsg_TypePropertySubscribeAck; break;
    case XMsg_TypePropertyStopSubscribe:    return XMsg_TypePropertyStopSubscribeAck; break;
    default: break;
    }
    return XMsg_TypeNone;
}

} //namespace XMessageHelper

XMessage::XMessage(uint8_t* buf)
    :msg_data_(buf) {
    //todo something
}

XMessage::~XMessage() {
    if (msg_data_) {
        delete[] msg_data_;
        msg_data_ = nullptr;
    }
}

XMessage::XMessageList XMessage::unpack(uint32_t pkt_size) {
    uint32_t base_hdr_size = XMSG_BASE_HDR_SIZE;
    uint32_t tp_hdr_size = XMessageHelper::GetHeaderLength(XMSG_FLAGS_TP_FLAG_MASK);
    uint32_t max_tp_msg_size = pkt_size + tp_hdr_size;
    uint32_t msg_payload_size = payload_size();
    uint32_t total_pkg = (msg_payload_size % max_tp_msg_size) ? ((msg_payload_size / max_tp_msg_size) + 1) : (msg_payload_size / max_tp_msg_size);
    uint8_t* pdata = payload();
    uint8_t* pend = pdata + msg_payload_size;
    uint32_t pkg_idx = XMSG_PKT_IDX_START;
    XMessageList msg_list;
    
    while (pdata < pend) {
        uint32_t left_size = static_cast<uint32_t>(pend - pdata);
        uint32_t payload_size = (left_size > max_tp_msg_size) ? max_tp_msg_size : left_size;
        uint32_t length = payload_size + tp_hdr_size;
        uint8_t* buf = new uint8_t[length];
        memcpy(buf, pdata, base_hdr_size); /** initialize base header info */
        XMessageHelper::ByteWrite4(buf, length);

        XMessageSp msg_ptr{new XMessage(buf)};
        XMSG_FLAGS_TP_FLAG_ENABLE(buf[XMSG_FIELD_OFFSET_FLAGS]);
        
        msg_ptr->set_pkt_total(total_pkg);
        msg_ptr->set_pkt_idx(pkg_idx++);
        msg_list.push_back(msg_ptr);

        pdata += payload_size;       
    }

    return msg_list;    
}

XMessageSp XMessage::clone() {
    if (!msg_data_) { return nullptr;  }
    uint32_t size = length();
    uint8_t* data = new uint8_t[size];
    if (!data) { return nullptr; }

    XMessageSp msg = std::make_shared<XMessage>(data);

    if (!msg) { 
        delete[] data; 
        return nullptr; 
    }

    memcpy(data, msg_data_, size);

    return msg;
}

/**
 * payload: 
 * if != nullptr, then Create XMessage and copy payload data.
 * if == nullptr, just Create XMessage with 'payload_size'
*/
//static 
XMessageSp XMessage::CreateMsg(uint8_t msg_type, uint32_t payload_size, const uint8_t* payload /*= nullptr*/) {
    return CreateMsg(msg_type, 0, 0, payload_size, payload);
}

//static 
XMessageSp XMessage::CreateMsg(uint8_t msg_type, MessageId msg_id, uint32_t payload_size, const uint8_t* payload /*= nullptr*/) {
    return CreateMsg(msg_type, msg_id, 0, payload_size, payload);
}

//static 
XMessageSp XMessage::CreateMsg(uint8_t msg_type, MessageId msg_id, SequenceNumber seq_num, uint32_t payload_size, const uint8_t* payload /*= nullptr*/) {
    uint32_t length = XMSG_BASE_HDR_SIZE + payload_size;
    uint8_t* buf = new uint8_t[length];

    if (buf) {
        XMessageSp msg_ptr{new XMessage(buf)};
        XMessageHelper::InitializeMessageHeader(buf, length, XMSG_PROTOCOL_VER, msg_type, msg_id, seq_num);
        if (payload) {
            memcpy(msg_ptr->payload(), payload, payload_size);
        }
        return msg_ptr;
    }

    return nullptr;    
}

//static 
XMessageSp XMessage::CreateTpMsg(uint8_t msg_type, uint32_t payload_size, const uint8_t* payload /*= nullptr*/ ) {
    uint32_t length = XMessageHelper::GetHeaderLength(XMSG_FLAGS_TP_FLAG_MASK) + payload_size;
    uint8_t* buf = new uint8_t[length];

    if (buf) {
        XMessageSp msg_ptr{new XMessage(buf)};
        XMessageHelper::InitializeMessageHeader(buf, length, XMSG_FLAGS_TP_FLAG_MASK);
        return msg_ptr;
    }

    return nullptr;
}

//static 
XMessageSp XMessage::CreateMsg(const uint8_t* hdr_data, uint32_t hdr_size) {
    uint32_t length = XMessageHelper::MsgHdrGetLength(hdr_data);
    uint8_t* buf = new uint8_t[length];

    if (buf) {
        XMessageSp msg_ptr{new XMessage(buf)};
        memcpy(buf, hdr_data, hdr_size);
        return msg_ptr;
    }

    return nullptr;
}


/**byte buffer data to 'XMessage' structure,
 * msg_data: will relase by XMessage destruction function
 */
//static 
XMessageSp XMessage::BuildMsg(uint8_t* msg_data, uint32_t msg_length) {
    if (!msg_data || msg_length < XMSG_BASE_HDR_SIZE) { return nullptr; }
    if (!XMessageHelper::CheckMessageHeader(msg_data, msg_length)) { return  nullptr; }

    XMessageSp msg_ptr{new XMessage(msg_data)};

    return msg_ptr;
}

//static 
XMessageSp XMessage::CopyMsg(const uint8_t* msg_data, uint32_t msg_length) {
    if (!msg_data || msg_length < XMSG_BASE_HDR_SIZE) { return nullptr; }

    if (!XMessageHelper::CheckMessageHeader(msg_data, msg_length)) { return  nullptr; }

    uint8_t* buf = new uint8_t[msg_length];
    if (buf) {
        memcpy(buf, msg_data, msg_length);
        XMessageSp msg_ptr{new XMessage(buf)}; 
        return msg_ptr;
    }

    return nullptr;
}

} //namespace shmipc::com